#include "drv_spi_w25qxx.h"
#include "bsp.h"
#if (CONFIG_OS_ENABLE == 1)
#include "os_sal.h"
#endif

/* w25qxx spi flash 系列驱动 */

//4Kbytes为一个Sector
//16个扇区为1个Block
//W25Q128
//容量为16M字节,256个Block,4096个Sector 

//W25X系列/Q系列芯片列表	   
//W25Q80 ID  0XEF13
//W25Q16 ID  0XEF14
//W25Q32 ID  0XEF15
//W25Q64 ID  0XEF16	
#define W25Q80 	0XEF13 	
#define W25Q16 	0XEF14
#define W25Q32 	0XEF15
#define W25Q64 	0XEF16
#define W25Q128	0XEF17

//指令表
#define W25X_CMD_WriteEnable		0x06 
#define W25X_CMD_WriteDisable		0x04 
#define W25X_CMD_ReadStatusReg		0x05 
#define W25X_CMD_WriteStatusReg		0x01 
#define W25X_CMD_ReadData			0x03 
#define W25X_CMD_FastReadData		0x0B 
#define W25X_CMD_FastReadDual		0x3B 
#define W25X_CMD_PageProgram		0x02 
#define W25X_CMD_BlockErase			0xD8 
#define W25X_CMD_SectorErase		0x20 
#define W25X_CMD_ChipErase			0xC7 
#define W25X_CMD_PowerDown			0xB9 
#define W25X_CMD_ReleasePowerDown	0xAB 
#define W25X_CMD_DeviceID			0xAB 
#define W25X_CMD_ManufactDeviceID	0x90 
#define W25X_CMD_JedecDeviceID		0x9F 

///////////////////////////////////////////////

#define W25QXX_IO_CS_SET()		    	bsp_gpio_pin_set(GPIOE, GPIO_PIN_11)
#define W25QXX_IO_CS_CLR()		    	bsp_gpio_pin_clr(GPIOE, GPIO_PIN_11)

#define w25qxx_io_read_write_byte(x)    bsp_spi4_read_write_byte(x)
#define w25qxx_io_read_buff(p, s)       bsp_spi4_read_buff(p, s)
#define w25qxx_io_write_buff(p, s)		bsp_spi4_write_buff(p, s)

#define w25qxx_io_lock()                bsp_spi4_lock()
#define w25qxx_io_unlock()              bsp_spi4_unlock()


/* static var */

typedef struct
{
    uint16_t id;        // 芯片ID
    uint32_t size;      // 内存大小(字节)
} _w25qxx_info_t;

static const _w25qxx_info_t w25qxx_info = 
{
#if defined(CONFIG_HW_SPI_FLASH_W25Q128)
    .id = W25Q128,
    .size = 16 * 1024 * 1024,
#elif defined(CONFIG_HW_SPI_FLASH_W25Q64)
    .id = W25Q64,
    .size = 8 * 1024 * 1024,
#elif defined(CONFIG_HW_SPI_FLASH_W25Q32)
    .id = W25Q32,
    .size = 4 * 1024 * 1024,
#else
  #error "SPI-Flash driver not defined or support."
#endif    
};

static uint8_t w25qxx_read_cache[4096];

#if (CONFIG_OS_ENABLE == 1)
static os_mutex_handle_t w25qxx_mutex = NULL;
#endif


/* export func */

/**
 * @brief 初始化
 * 
 * @return int 
 */
int drv_spi_w25qxx_init(void)
{
    uint32_t id;
    GPIO_InitTypeDef GPIO_InitStruct;

    __HAL_RCC_GPIOE_CLK_ENABLE();

    /* SPI_FLASH_CS : PE11 */
    GPIO_InitStruct.Pin = GPIO_PIN_11;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

    W25QXX_IO_CS_SET(); // SPI FLASH不选中

    bsp_spi4_init();    //初始化SPI
    bsp_spi4_set_speed(SPI_BAUDRATEPRESCALER_2); // 2分频

#if (CONFIG_OS_ENABLE == 1)
    w25qxx_mutex = os_mutex_create();
#endif

    id = drv_spi_w25qxx_read_id(); //读取FLASH ID.
    // printf("SPI W25Q128 id = 0x0%X\r\n", id);
    if (id != w25qxx_info.id)
    {
        printf("spi-w25q128 error id = 0x0%X\r\n", id);
        return -1;
    }

    return 0;
}

/**
 * @brief 读取芯片ID W25Q128的ID:0XEF17
 *
 * @return uint32_t
 */
uint32_t drv_spi_w25qxx_read_id(void)
{
    uint32_t val = 0;

    w25qxx_io_lock();
    W25QXX_IO_CS_CLR();
    w25qxx_io_read_write_byte(0x90); //发送读取ID命令
    w25qxx_io_read_write_byte(0x00);
    w25qxx_io_read_write_byte(0x00);
    w25qxx_io_read_write_byte(0x00);
    val |= w25qxx_io_read_write_byte(0xFF) << 8;
    val |= w25qxx_io_read_write_byte(0xFF);
    W25QXX_IO_CS_SET();
    w25qxx_io_unlock();
    return val;
}

/**
 * @brief 读取SPI_FLASH的状态寄存器
 *        BIT7  6   5   4   3   2   1   0
 *        SPR   RV  TB BP2 BP1 BP0 WEL BUSY
 *        SPR:默认0,状态寄存器保护位,配合WP使用
 *        TB,BP2,BP1,BP0:FLASH区域写保护设置
 *        BUSY:忙标记位(1,忙;0,空闲)
 *        WEL:写使能锁定
 *        默认:0x00
 * 
 * @return uint8_t 
 */
uint8_t drv_spi_w25qxx_read_sr(void)
{
    uint8_t byte = 0;

    w25qxx_io_lock();
    W25QXX_IO_CS_CLR();
    w25qxx_io_read_write_byte(W25X_CMD_ReadStatusReg); //发送读取状态寄存器命令
    byte = w25qxx_io_read_write_byte(0Xff);    //读取一个字节
    W25QXX_IO_CS_SET();
    w25qxx_io_unlock();
    return byte;
}

/**
 * @brief 写SPI_FLASH状态寄存器
 *        只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写!!!
 * @param sr 
 */
void drv_spi_w25qxx_write_sr(uint8_t sr)
{
    w25qxx_io_lock();
    W25QXX_IO_CS_CLR();
    w25qxx_io_read_write_byte(W25X_CMD_WriteStatusReg); //发送写取状态寄存器命令
    w25qxx_io_read_write_byte(sr);                      //写入一个字节
    W25QXX_IO_CS_SET();
    w25qxx_io_unlock();
}

/**
 * @brief 写使能(将WEL置位)
 * 
 */
void drv_spi_w25qxx_write_enable(void)
{
    w25qxx_io_lock();
    W25QXX_IO_CS_CLR();
    w25qxx_io_read_write_byte(W25X_CMD_WriteEnable); //发送写使能
    W25QXX_IO_CS_SET();
    w25qxx_io_unlock();
}

/**
 * @brief 写禁止(将WEL清零)
 * 
 */
void drv_spi_w25qxx_write_disable(void)
{
    w25qxx_io_lock();
    W25QXX_IO_CS_CLR();
    w25qxx_io_read_write_byte(W25X_CMD_WriteDisable); //发送写禁止指令
    W25QXX_IO_CS_SET();
    w25qxx_io_unlock();
}

/**
 * @brief 等待空闲
 *
 */
void drv_spi_w25qxx_wait_busy(void)
{
    while ((drv_spi_w25qxx_read_sr() & 0x01) == 0x01); // 等待BUSY位清空    
}

/**
 * @brief 读取指定地址的指定长度数据
 *
 * @param addr 读取地址(24bit)
 * @param pbuffer 数据缓存
 * @param size 读取数量
 */
void drv_spi_w25qxx_read(uint32_t addr, uint8_t *pbuffer, uint16_t size)
{
    uint8_t cmd[4];

    w25qxx_io_lock();
    W25QXX_IO_CS_CLR(); //使能器件    
    cmd[0] = W25X_CMD_ReadData;  // 发送读取命令
    cmd[1] = (uint8_t)(addr >> 16); // 发送24bit地址
    cmd[2] = (uint8_t)(addr >> 8);
    cmd[3] = (uint8_t)(addr);
    w25qxx_io_write_buff(cmd, 4);
    w25qxx_io_read_buff(pbuffer, size);
    W25QXX_IO_CS_SET(); //取消片选
    w25qxx_io_unlock();
}

/**
 * @brief SPI在一页(0~65535)内写入少于256个字节的数据
 *        在指定地址开始写入最大256字节的数据
 * 
 * @param addr 地址
 * @param pbuffer 数据缓存
 * @param size 数据大小,最大256,该数不应该超过该页的剩余字节数!!!
 */
void drv_spi_w25qxx_write_page(uint32_t addr, uint8_t *pbuffer, uint16_t size)
{
    uint8_t cmd[4];

    drv_spi_w25qxx_write_enable();  // SET WEL

    w25qxx_io_lock();
    W25QXX_IO_CS_CLR();
    cmd[0] = W25X_CMD_PageProgram;  // 发送写页命令
    cmd[1] = (uint8_t)(addr >> 16); // 发送24bit地址
    cmd[2] = (uint8_t)(addr >> 8);
    cmd[3] = (uint8_t)(addr);
    w25qxx_io_write_buff(cmd, 4);
    w25qxx_io_write_buff(pbuffer, size);
    W25QXX_IO_CS_SET();
    w25qxx_io_unlock();
    drv_spi_w25qxx_wait_busy(); //等待写入结束
}

/**
 * @brief 无检验写SPI FLASH,必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
 *        具有自动换页功能,在指定地址开始写入指定长度的数据,但是要确保地址不越界!
 * 
 * @param addr 地址
 * @param pbuffer 数据缓存
 * @param size 数据大小,最大65535
 */
void drv_spi_w25qxx_write_nocheck(uint32_t addr, uint8_t *pbuffer, uint16_t size)
{
    uint16_t pageremain;
    pageremain = 256 - addr%256; //单页剩余的字节数
    if (size <= pageremain) pageremain = size; //不大于256个字节
    while (1)
    {
        drv_spi_w25qxx_write_page(addr, pbuffer, pageremain);
        if (size == pageremain) break; //写入结束了
        else       // size>pageremain
        {
            pbuffer += pageremain;
            addr += pageremain;

            size -= pageremain; //减去已经写入了的字节数
            if (size > 256) pageremain = 256; //一次可以写入256个字节
            else pageremain = size; //不够256个字节了
        }
    }
}

/**
 * @brief 写SPI FLASH,在指定地址开始写入指定长度的数据,函数内带擦除操作!
 * 
 * @param addr 地址
 * @param pbuffer 数据缓存
 * @param size 数据大小,最大65535
 */
void drv_spi_w25qxx_write(uint32_t addr, uint8_t *pbuffer, uint16_t size)
{
    uint32_t secpos;
    uint16_t secoff;
    uint16_t secremain;
    uint16_t i;

    secpos = addr / 4096;      //扇区地址
    secoff = addr % 4096;      //在扇区内的偏移
    secremain = 4096 - secoff; //扇区剩余空间大小

    if (size <= secremain) secremain = size; //不大于4096个字节
    while (1)
    {
        drv_spi_w25qxx_read(secpos * 4096, w25qxx_read_cache, 4096); //读出整个扇区的内容
        for (i = 0; i < secremain; i++) //校验数据
        {
            if (w25qxx_read_cache[secoff + i] != 0XFF) break; //需要擦除
        }
        if (i < secremain) //需要擦除
        {
            drv_spi_w25qxx_erase_sector(secpos); //擦除这个扇区
            for (i = 0; i < secremain; i++)      //复制
            {
                w25qxx_read_cache[i + secoff] = pbuffer[i];
            }
            drv_spi_w25qxx_write_nocheck(secpos * 4096, w25qxx_read_cache, 4096); //写入整个扇区
        }
        else
        {
            drv_spi_w25qxx_write_nocheck(addr, pbuffer, secremain); //写已经擦除了的,直接写入扇区剩余区间.
        }
        if (size == secremain) break; //写入结束了
        else    //写入未结束
        {
            secpos++;   //扇区地址增1
            secoff = 0; //偏移位置为0

            pbuffer += secremain; //指针偏移
            addr += secremain;    //写地址偏移
            size -= secremain;    //字节数递减
            if (size > 4096) secremain = 4096; //下一个扇区还是写不完
            else secremain = size; //下一个扇区可以写完了
        }
    }
}

/**
 * @brief 擦除整个芯片
 * 整片擦除时间:
 * W25X16:25s
 * W25X32:40s
 * W25X64:40s
 * 容量越大时间越长
 */
void drv_spi_w25qxx_erase(void)
{
    drv_spi_w25qxx_write_enable(); // SET WEL
    drv_spi_w25qxx_wait_busy();

    w25qxx_io_lock();
    W25QXX_IO_CS_CLR();
    w25qxx_io_read_write_byte(W25X_CMD_ChipErase); //发送片擦除命令
    W25QXX_IO_CS_SET();
    w25qxx_io_unlock();
    drv_spi_w25qxx_wait_busy(); //等待芯片擦除结束
}

/**
 * @brief 擦除一个扇区
 *
 * @param addr 扇区地址 0~2047 for W25Q64
 *             擦除一个山区的最少时间:150ms
 */
void drv_spi_w25qxx_erase_sector(uint32_t addr)
{
    uint8_t cmd[4];

    addr *= 4096;
    drv_spi_w25qxx_write_enable(); // SET WEL
    w25qxx_io_lock();
    W25QXX_IO_CS_CLR();
    cmd[0] = W25X_CMD_SectorErase;  // 发送扇区擦除指令
    cmd[1] = (uint8_t)(addr >> 16); // 发送24bit地址
    cmd[2] = (uint8_t)(addr >> 8);
    cmd[3] = (uint8_t)(addr);
    w25qxx_io_write_buff(cmd, 4);
    W25QXX_IO_CS_SET();
    w25qxx_io_unlock();
    drv_spi_w25qxx_wait_busy(); //等待擦除完成
}

/**
 * @brief 进入掉电模式
 * 
 */
void drv_spi_w25qxx_power_down(void)
{
    W25QXX_IO_CS_CLR();
    w25qxx_io_lock();
    w25qxx_io_read_write_byte(W25X_CMD_PowerDown); //发送掉电命令
    w25qxx_io_unlock();
    W25QXX_IO_CS_SET();
    delay_us(3);    //等待TPD
}

/**
 * @brief 唤醒
 * 
 */
void drv_spi_w25qxx_wakeup(void)
{
    W25QXX_IO_CS_CLR();
    w25qxx_io_lock();
    w25qxx_io_read_write_byte(W25X_CMD_ReleasePowerDown); //  send W25X_PowerDown command 0xAB
    w25qxx_io_unlock();
    W25QXX_IO_CS_SET();
    delay_us(3); //等待TRES1
}

/**
 * @brief 由存储地址获取扇区序号
 * 
 * @param addr 地址
 * @return uint32_t 扇区序号
 */
uint32_t drv_spi_w25qxx_get_sector_from_addr(uint32_t addr)
{
    return (addr / 4096); //扇区地址
}

void drv_spi_w25qxx_lock(void)
{
#if (CONFIG_OS_ENABLE == 1)
    os_mutex_take(w25qxx_mutex, OS_WAIT_MAX);
#endif
}

void drv_spi_w25qxx_unlock(void)
{
#if (CONFIG_OS_ENABLE == 1)
    os_mutex_release(w25qxx_mutex);
#endif
}

/* export test */

//读取扇区数据,打印
void drv_spi_w25qxx_read_test(uint32_t addr)
{
	uint32_t secpos;
	uint16_t i;
	secpos=addr/4096;//扇区地址 
	drv_spi_w25qxx_read(secpos*4096,w25qxx_read_cache,4096);//读出整个扇区的内容
	printf("addr : %x \r\n",addr);
	for(i=0;i<4096;i++)
	{
		printf("%02x ",w25qxx_read_cache[i]);
	}
	printf(" \r\n");
}

#if 0
// #if (CONFIG_SHELL_ENABLE == 1)

#include "shell.h"

int w25qxx_test(int argc, char **argv)
{
    uint32_t addr;
    uint32_t size;
    int i;
    uint8_t buff[4096];

    addr = w25qxx_info.size - 4096;
    size = sizeof(buff);

    printf("drv_spi_w25qxx test:\r\n");
    
    printf("write...\r\n");
    for (i = 0; i < size; i++)
    {
        buff[i] = (uint8_t)(i & 0xFF);
    }
    drv_spi_w25qxx_write(addr, buff, size);

    printf("read...\r\n");
    memset(buff, 0x00, size);
    drv_spi_w25qxx_read(addr, buff, size);

    printf("verify...\r\n");
    for (i = 0; i < size; i++)
    {
        if (buff[i] != (i & 0xFF))
        {
            printf("fail in %d\r\n", i);
            return -1;
        }
    }

    printf("finsh\r\n");

    return 0;
}
SHELL_CMD_EXPORT(w25qxx_test, w25qxx mem write read test);

// int w25qxx_erase_chip(int argc, char **argv)
// {
//     printf("erase chip...");
//     drv_spi_w25qxx_erase();
//     return 0;
// }
// SHELL_CMD_EXPORT(w25qxx_erase_chip, w25qxx erase chip);

int w25qxx_read_sr(int argc, char **argv)
{
    uint8_t sr1, sr2, sr3;

    W25QXX_IO_CS_CLR();
    w25qxx_io_read_write_byte(W25X_CMD_ReadStatusReg); //发送读取状态寄存器命令
    sr1 = w25qxx_io_read_write_byte(0Xff);    //读取一个字节
    sr2 = w25qxx_io_read_write_byte(0Xff);    //读取一个字节
    sr3 = w25qxx_io_read_write_byte(0Xff);    //读取一个字节
    W25QXX_IO_CS_SET();
    printf("sr1 = 0x%02X\r\n", sr1);
    printf("sr2 = 0x%02X\r\n", sr2);
    printf("sr3 = 0x%02X\r\n", sr3);

    return 0;
}
SHELL_CMD_EXPORT(w25qxx_read_sr, w25qxx read sr);


#endif
